/**
 * 版权所有@2015 北京博创联动科技有限公司；未经许可，不得擅自复制、传播；
 */
package com.uptech.homer.beans;

import com.uptech.homer.itf.Identify;
import com.uptech.homer.util.ByteUtils;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 消息数据结构
 *
 * @author lei
 */
public class Message implements Serializable, Identify {
    private static final long serialVersionUID = 1L;

    /** 消息头 */
    private Head head = new Head();

    /** 消息体 */
    private List<Body> bodys = new ArrayList<Body>();

    /** 校验码 */
    private byte[] checkCode = new byte[1];

    /** 消息创建时间 */
    private long createTime = System.currentTimeMillis();

    /*************************** 以下两个私有属性不应该被序列化 */
    // 随机通信校验码；可为空
    private byte[] _randomKey;

    // 用于快速搜索消息体
    private Map<String, Body> _bodyTypeMap = new HashMap<String, Body>();

    @Override
    public String getDeviceId() {
        return getHead().getDeviceIdToString();
    }

    public Head getHead() {
        return head;
    }

    public void setHead(Head head) {
        this.head = head;
    }

    public <T> T findBody(Class<T> bodyClass) {
        return findBody(bodyClass, false);
    }

    /**
     * 查找指定类型的消息体
     *
     * @param bodyClass
     * @param isParent  是否包含父类
     *
     * @return
     */
    public <T> T findBody(Class<T> bodyClass,
                          boolean isParent) {

        checkAndInitBodyTypeMap();


        // 按照类名快速搜索
        if (bodyClass != null && _bodyTypeMap.containsKey(bodyClass.getName())) {
            return bodyClass.cast(_bodyTypeMap.get(bodyClass.getName()));
        }

        // 如果isParent为True，则要考虑父类
        if (isParent && bodys != null && !bodys.isEmpty()) {
            for (Body body : bodys) {

                if (bodyClass.isAssignableFrom(body.getClass())) {
                    return bodyClass.cast(body);
                }
            }
        }

        return null;
    }

    /**
     * 查找指定类型的消息体
     *
     * @param typeCode
     *
     * @return
     */
    public List<Body> findBody(int... typeCode) {
        List<Body> bodyList = null;

        if (typeCode == null || typeCode.length == 0) {
            return null;
        }

        if (bodys != null && !bodys.isEmpty()) {
            bodyList = new ArrayList<Body>();

            bodysIterator:
            for (Body body : bodys) {
                int bodyTypeCode = ByteUtils.getUnsignedShort(body.getType());

                // 查找当前消息体类型是否在给定的类型列表内
                for (int type : typeCode) {

                    if (bodyTypeCode == type) {
                        bodyList.add(body);
                        continue bodysIterator;
                    }
                }
            }
        }

        return bodyList;
    }

    public int getBodySize() {
        return bodys.size();
    }

    public Body getBody(int index) {
        return bodys.get(index);
    }

    public Message addBody(Body body) {

        // 将消息体存入列表
        bodys.add(body);
        // 将消息体存入类型映射表，便于查找
        _bodyTypeMap.put(body.getClass().getName(), body);

        return this;
    }

    public void removeBody(Body body) {
        bodys.remove(body);
    }

    public void setBodys(List<Body> bodys) {
        this.bodys = bodys;

        this._bodyTypeMap = null;
        checkAndInitBodyTypeMap();
    }

    public byte[] getCheckCode() {
        return checkCode;
    }

    public void setCheckCode(byte[] checkCode) {
        this.checkCode = checkCode;
    }

    public byte[] getRandomKey() {
        return _randomKey;
    }

    public Message setRandomKey(byte[] randomKey) {
        _randomKey = randomKey;
        return this;
    }

    public long getCreateTime() {
        return createTime;
    }

    private void checkAndInitBodyTypeMap() {

        if ((_bodyTypeMap == null || _bodyTypeMap.isEmpty()) && bodys != null && !bodys.isEmpty()) {

            if (_bodyTypeMap == null) {
                _bodyTypeMap = new HashMap<String, Body>();
            }

            for (Body body : bodys) {
                _bodyTypeMap.put(body.getClass().getName(), body);
            }
        }
    }

    /**
     * 序列化
     *
     * @return
     */
    public byte[] serialize() {

        List<byte[]> bodyBytesList = new ArrayList<byte[]>();

        // 计算消息体长度及消息总长度
        int dataLength = 0, serialAryLength = 0;

        // 首先将消息体分别序列化
        if (bodys != null && bodys.size() > 0) {

            for (Body body : bodys) {
                byte[] bodyBytes = body.getData();
                bodyBytesList.add(bodyBytes);

                // 累加消息体长度及消息总长度
                dataLength += bodyBytes.length;
                serialAryLength += bodyBytes.length;
            }
        }

        // 更新消息头中的相关字段
        head.setBodyCount(bodys.size());
        head.setDataLength(dataLength);

        // 然后再将消息头序列化
        byte[] headBytes = head.serialize();
        serialAryLength += headBytes.length;

        // 创建定长的字节数组，用于存储序列化数据
        byte[] serialBytes = new byte[serialAryLength + checkCode.length];

        // 准备将消息序列化
        int serialAryIdx = 0;

        // 拷贝消息头至序列化数组
        System.arraycopy(headBytes, 0, serialBytes, serialAryIdx, headBytes.length);
        serialAryIdx += headBytes.length;

        // 拷贝消息体至序列化数组
        if (bodyBytesList.size() > 0) {

            for (byte[] bodyBytes : bodyBytesList) {
                System.arraycopy(bodyBytes, 0, serialBytes, serialAryIdx, bodyBytes.length);
                serialAryIdx += bodyBytes.length;
            }
        }

        // 重新计算验证码
        updateCheckCode(serialBytes, head.getFrameHead().length, serialAryLength);

        // 拷贝验证码至序列化数组
        System.arraycopy(checkCode, 0, serialBytes, serialAryIdx, checkCode.length);

        return serialBytes;
    }

    /**
     * 重新计算验证码
     *
     * @param serialBytes 需要计算校验码的数组
     * @param beginIdx    给定的数组起始索引(含)
     * @param endIdx      给定的数组截止索引(不含)
     */
    private void updateCheckCode(byte[] serialBytes,
                                 int beginIdx,
                                 int endIdx) {
        checkCode[0] = 0x0;

        if (endIdx > beginIdx && endIdx <= serialBytes.length) {

            int keyLength = head.getDeviceId().length + (_randomKey != null ? _randomKey.length : 0);
            byte[] key = ByteUtils.fill(new byte[keyLength], head.getDeviceId(), _randomKey);

            checkCode[0] = (byte) ByteUtils.checkXOR(serialBytes, key, beginIdx, endIdx);
        }
    }

    /**
     * **********************************************************************
     * 消息头
     *
     * @author lei
     */
    public static final class Head implements Serializable {
        private static final long serialVersionUID = 1L;

        /** 帧头 */
        private byte[] frameHead = new byte[]{(byte) 0xF1, (byte) 0xF2, (byte) 0xFF};

        /** 消息ID */
        private int msgFuncId;

        /** 设备ID */
        private byte[] deviceId;
        /** 消息流水号 */
        private byte[] serialNum;

        /** 消息体个数 */
        private byte[] bodyCount;
        /** 数据长度 */
        private byte[] dataLength;

        public byte[] getFrameHead() {
            return frameHead;
        }

        public void setFrameHead(byte[] frameHead) {
            this.frameHead = frameHead;
        }

        public int getMsgFuncId() {
            return msgFuncId;
        }

        public void setMsgFuncId(int msgFuncId) {
            this.msgFuncId = msgFuncId;
        }

        public byte[] getDeviceId() {
            return deviceId;
        }

        public void setDeviceId(String deviceId) {

            try {
                this.deviceId = deviceId.getBytes("GBK");
            } catch (UnsupportedEncodingException e) {
                this.deviceId = deviceId.getBytes();
            }
        }

        public void setDeviceId(byte[] deviceId) {
            this.deviceId = deviceId;
        }

        public String getDeviceIdToString() {

            // 去掉开头/结尾的“0x00”字节
            int beginIdx = 0, endIdx = 0;
            while (beginIdx < deviceId.length && deviceId[beginIdx] == 0x0)
                beginIdx++;

            endIdx = beginIdx;
            while (endIdx < deviceId.length && deviceId[endIdx] != 0x0)
                endIdx++;

            byte[] strBytesAry = new byte[endIdx - beginIdx];
            System.arraycopy(deviceId, beginIdx, strBytesAry, 0, strBytesAry.length);

            return new String(strBytesAry);
        }

        public byte[] getSerialNum() {
            return serialNum;
        }

        public void setSerialNum(int serialNum) {

            // 消息流水号占用两个字节
            this.serialNum = new byte[2];

            this.serialNum[0] = (byte) (serialNum & 0xFF);
            this.serialNum[1] = (byte) ((serialNum >>> 8) & 0xFF);
        }

        public void setSerialNum(byte[] serialNum) {
            this.serialNum = serialNum;
        }

        public int getSerialNumVal() {
            return ByteUtils.getUnsignedShort(serialNum);
        }

        public byte[] getBodyCount() {
            return bodyCount;
        }

        public void setBodyCount(int bodyCount) {

            // 消息体个数占用一个字节
            this.bodyCount = new byte[1];

            this.bodyCount[0] = (byte) bodyCount;
        }

        public void setBodyCount(byte[] bodyCount) {
            this.bodyCount = bodyCount;
        }

        public int getBodyCountVal() {
            return ByteUtils.getUnsignedShort(bodyCount);
        }

        public byte[] getDataLength() {
            return dataLength;
        }

        public void setDataLength(int dataLength) {

            // 数据长度占用两个字节
            this.dataLength = new byte[2];

            this.dataLength[0] = (byte) (dataLength & 0xFF);
            this.dataLength[1] = (byte) ((dataLength >>> 8) & 0xFF);
        }

        public void setDataLength(byte[] dataLength) {
            this.dataLength = dataLength;
        }

        public int getDataLengthVal() {
            return ByteUtils.getUnsignedShort(dataLength);
        }

        /**
         * 序列化
         *
         * @return
         */
        public byte[] serialize() {

            if (frameHead != null && deviceId != null && serialNum != null && bodyCount != null && dataLength != null) {

                // 创建定长的字节数组，用于存储序列化数据
                byte[] serialBytes = new byte[frameHead.length + 1 + deviceId.length + serialNum.length + bodyCount.length + dataLength.length];

                // 数据序列化
                return ByteUtils.fill(serialBytes, frameHead, ByteUtils.getBytes(msgFuncId, new byte[1]), deviceId, serialNum, bodyCount, dataLength);
            } else {
                throw new NullPointerException("The key information is not complete");
            }
        }
    }

    /**
     * 消息体
     *
     * @author lei
     */
    public static class Body implements Serializable {
        private static final long serialVersionUID = 1L;

        /** 数据包内容；根据不同的消息体类型，此包内存储的数据会不同。请按照相关协议取值 */
        protected byte[] data;

        public byte[] getData() {
            return data;
        }

        public Body setData(byte[] data) {
            this.data = data;
            return this;
        }

        /** 类型编码 */
        public byte[] getType() {
            return ByteUtils.getRange(getData(), 0, 2);
        }

        /** 消息体长度 */
        public byte[] getLength() {
            return ByteUtils.getRange(getData(), 2, 4);
        }
    }
}
